﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.DataMining.ClusterAnalysis
{
    /// <summary>
    /// Defines the distance calculation mode for cluster analysis.
    /// </summary>
    public enum DistanceCalculationMode
    {
        /// <summary>
        /// Uses the euclidean distance calculation method.
        /// </summary>
        Euclidean = 0,
        /// <summary>
        /// Uses the dot product distance calculation method.
        /// </summary>
        DotProduct = 1,
        /// <summary>
        /// Uses the manhattan distance calculation method.
        /// </summary>
        Manhattan = 2,
        /// <summary>
        /// Uses the cosine distance calculation method.
        /// </summary>
        Cosine = 3,
        /// <summary>
        /// Uses the covariance distance calculation method.
        /// </summary>
        Covariance = 4
    }

    /// <summary>
    /// This class provides methods to compute the distance between two vectors in different ways.
    /// </summary>
    [Serializable]
    public static class DistanceComputation
    {
        /// <summary>
        /// The global precision of the calculation.
        /// </summary>
        public const double Epsilon = 1e-10;

        /// <summary>
        /// Computes the distance between two vectors by using the euclidean distance calculation method.
        /// </summary>
        /// <param name="x">The first vector.</param>
        /// <param name="y">The second vector.</param>
        /// <returns>The distance between the two specified vectors.</returns>
        public static double EuclideanDistance(GeneralVector x, GeneralVector y)
        {
            if (x == (GeneralVector) null)
            {
                throw new ArgumentNullException("x");
            }

            if (y == (GeneralVector) null)
            {
                throw new ArgumentNullException("y");
            }

            double result = 0;

            for (int i = 0; i < x.Count; i++)
            {
                result += (x[i] - y[i]) * (x[i] - y[i]);
            }

            return Math.Sqrt(result);
        }

        /// <summary>
        /// Computes the distance between two vectors by using the dot product distance calculation method.
        /// </summary>
        /// <param name="x">The first vector.</param>
        /// <param name="y">The second vector.</param>
        /// <returns>The distance between the two specified vectors.</returns>
        public static double DotProductDistance(GeneralVector x, GeneralVector y)
        {
            if (x == (GeneralVector) null)
            {
                throw new ArgumentNullException("x");
            }

            if (y == (GeneralVector) null)
            {
                throw new ArgumentNullException("y");
            }

            double result = 0;

            for (int i = 0; i < x.Count; i++)
            {
                result += x[i] * y[i];
            }

            if (x.Count == 0)
            {
                return 0;
            }

            return result / x.Count;
        }

        /// <summary>
        /// Computes the distance between two vectors by using the manhattan distance calculation method.
        /// </summary>
        /// <param name="x">The first vector.</param>
        /// <param name="y">The second vector.</param>
        /// <returns>The distance between the two specified vectors.</returns>
        public static double ManhattanDistance(GeneralVector x, GeneralVector y)
        {
            if (x == (GeneralVector) null)
            {
                throw new ArgumentNullException("x");
            }

            if (y == (GeneralVector) null)
            {
                throw new ArgumentNullException("y");
            }

            double result = 0;

            for (int i = 0; i < x.Count; i++)
            {
                result += Math.Abs(x[i] - y[i]);
            }

            return result;
        }

        /// <summary>
        /// Computes the distance between two vectors by using the cosine distance calculation method.
        /// </summary>
        /// <param name="x">The first vector.</param>
        /// <param name="y">The second vector.</param>
        /// <returns>The distance between the two specified vectors.</returns>
        public static double CosineDistance(GeneralVector x, GeneralVector y)
        {
            if (x == (GeneralVector) null)
            {
                throw new ArgumentNullException("x");
            }

            if (y == (GeneralVector) null)
            {
                throw new ArgumentNullException("y");
            }

            double r = 0, x2 = 0, y2 = 0;

            for (int i = 0; i < x.Length; i++)
            {
                r += x[i] * y[i];
                x2 += x[i] * x[i];
                y2 += y[i] * y[i];
            }
            return r / (Math.Sqrt(x2 * y2) + DistanceComputation.Epsilon);
        }

        /// <summary>
        /// Computes the distance between two vectors by using the covariance distance calculation method.
        /// </summary>
        /// <param name="x">The first vector.</param>
        /// <param name="y">The second vector.</param>
        /// <returns>The distance between the two specified vectors.</returns>
        public static double CovarianceDistance(GeneralVector x, GeneralVector y)
        {
            if (x == (GeneralVector) null)
            {
                throw new ArgumentNullException("x");
            }

            if (y == (GeneralVector) null)
            {
                throw new ArgumentNullException("y");
            }

            if (x.Count == 0)
            {
                return 0;
            }

            double xavg = 0;
            double yavg = 0;
            double result = 0;

            for (int i = 0; i < x.Count; i++)
            {
                xavg += x[i];
                yavg += y[i];
            }

            xavg = xavg / x.Count;
            yavg = yavg / x.Count;

            for (int i = 0; i < x.Count; i++)
            {
                result += (x[i] - xavg) * (y[i] - yavg);
            }

            if (x.Count <= 1)
            {
                return 0;
            }

            return result / (x.Count - 1);
        }
    }
}